/* vccd/users.c 
 * 
 * This file is part of vccd. 
 * 
 * vccd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * vccd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with vccd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

#include <sys/mman.h>

#include <vcc/vcc.h>
#include <vcc/vccd.h>
#include <vcc/version.h>

#include <klist.h>


char 			*vccd_dir;
int 			nusers = 0, system_fd = 0;
struct klist_node 	logged_users = KLIST_NODE_INIT(&logged_users);


int users_init(void) {
	int fdt;

	if (unlikely((fdt = open("/etc/vccd-runtime", O_RDONLY)) < 0)) {
		perror("open()");

		exit(1);
	}

	if (unlikely(!(vccd_dir = amalloc(PATH_MAX)))) {
		vcc_log("*** malloc() failed\n");

		exit(1);
	}

	memset(vccd_dir, 0, PATH_MAX);

	if (unlikely(read(fdt, vccd_dir, PATH_MAX) < 0)) {
		perror("read()");

		exit(1);
	}

	close(fdt);

	vcc_log("vccd data directory at %s\n", vccd_dir);

	return 0;
}


static struct klist_node *find_user(int fd) {
	struct klist_node *n;

	for (n = logged_users.next; n != &logged_users; n = n->next) 
		if (n->value == fd) 
			return n;

	vcc_log("--- user not found: %d\n", fd);

	return NULL;
}


static struct user *find_user_name(char *usrname) {
	struct klist_node *n;
	struct user *u;

	for (n = logged_users.next; n != &logged_users; n = n->next) {
		u = n->data;

		if (!strncmp(u->username, usrname, USRNAME_SIZE)) 
			return u;
	}

	vcc_log("--- find_user_name: not found %s. \n", usrname);

	return NULL;
}


int open_user_file(char *usr) {
	char 	*fname;
	int 	fdt;

	if (unlikely(!(fname = amalloc(PATH_MAX)))) {
		vcc_log("*** malloc() failed\n");

		return -1;
	}

	sprintf(fname, "%s/users/%s", vccd_dir, usr);

	if (unlikely((fdt = open(fname, O_RDWR)) < 0)) {
		perror("open()");

		return -1;
	}

	afree(fname, PATH_MAX);

	return fdt;
}


int get_user(char *usr, struct user *buf) {
	int 		fdt;
	struct user	*u;

	if ((u = find_user_name(usr))) {
		memcpy(buf, u, sizeof(struct user));

		return 0;
	}

	if (unlikely(((fdt = open_user_file(usr)) == -1))) {
		vcc_log("*** user not found. \n");

		return -1;
	}

	read(fdt, buf, sizeof(struct user));
	close(fdt);

	return 0;
}

int sync_user(char *usr, struct user *buf) {
	struct user *u;
	int fdt;

	if ((u = find_user_name(usr))) 
		memcpy(u, buf, sizeof(struct user));

	if (unlikely((fdt = open_user_file(usr)) == -1)) {
		vcc_log("*** user not found. \n");

		return -1;
	}

	write(fdt, buf, sizeof(struct user));
	close(fdt);

	return 0;
}


int do_login_failed(int fd, struct vcc_request *out) {
	out->magic = htonl(VCC_MAGIC);
	out->reqtype = htonl(REQ_CTL_LOGIN);
	out->uid = 0;

	vcc_send_req(find_connection(fd), out);

	return 0;
}

int do_user_login(int fd, struct vcc_request *req) {
	struct user 		*usr;
	struct vcc_request 	*out;

	if (unlikely(!(out = amalloc(sizeof(struct vcc_request))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (unlikely(!(usr = amalloc(sizeof(struct user))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	if (get_user(req->usrname, usr) || strncmp(req->msg, usr->passwd, PASSWD_SIZE)) {
		vcc_log("%s:%s login failed. \n", req->usrname, req->msg);
		vcc_info("WLOGFAIL %s %s %d\n", req->usrname, req->msg, fd);

		do_login_failed(fd, out);
		afree(usr, sizeof(struct user));

		return 1;
	}

	init_user_info(fd, req->usrname, usr);

	return 0;
}


int do_ia_login(int fd, struct vcc_request *req) {
	struct user *u;

	if (unlikely(!(u = amalloc(sizeof(struct user))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	strcpy(u->username, req->usrname);
	init_user_info(fd, req->usrname, u);

	return 0;
}


int init_user_info(int fd, char *usrname, struct user *u) {
	struct klist_node 	*n;
	struct klist_node 	*p;
	static const char 	*join_msg = "%s logged in. now there are %d users. \n";
	char 			*t;
	struct vcc_request 	*out;

	if (unlikely(!(out = amalloc(sizeof(struct user))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	memset(out, 0, sizeof(struct user));

	if (!strncmp(usrname, "system", USRNAME_SIZE)) {
		if (system_fd) {
			vcc_log("system already logged-in. \n");
			do_login_failed(fd, out);

			afree(u, sizeof(struct user));

			return 1;
		}

		system_fd = fd;
	}

	vcc_log("%s logged in. \n", usrname);
	vcc_info("ISYSLOG %s %d\n", usrname, fd);

	if (unlikely(!(t = amalloc(128)))) {
		vcc_log("*** malloc() failed\n");
		afree(u, sizeof(struct user));

		return 1;
	}

	if (unlikely(!(n = amalloc(sizeof(struct klist_node))))) {
		vcc_log("*** malloc() failed\n");
		afree(u, sizeof(struct user));

		return 1;
	}

	memset(t, 0, 128);
	klist_init(n);

	n->value = fd;
	n->data = u;

	klist_add(&logged_users, n);
	p = find_connection(fd);

	p->stat = STAT_LOGGED;

	out->magic = htonl(VCC_MAGIC);
	out->reqtype = htonl(REQ_CTL_LOGIN);
	out->uid = htonl(1);
	vcc_send_req(p, out);

	nusers++;

	sprintf(t, join_msg, usrname, nusers);
	vcc_message_broadcast(t, "system", 0, 0);

	afree(t, 128);

	return 0;
}


int get_user_info(int fd, struct vcc_request *req) {
	struct vcc_request 	*out;
	struct user 		*u;

	if (unlikely(!(out = amalloc(sizeof(struct vcc_request))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	out->magic = htonl(VCC_MAGIC);
	out->reqtype = htonl(REQ_CTL_UINFO);
	u = (struct user *) out->msg;

	out->uid = htonl(get_user(req->msg, u));
	u->score = htonl(u->score);
	u->level = htonl(u->level);

	memset(u->passwd, 0, PASSWD_SIZE);
	vcc_send_req(find_connection(fd), out);

	return 0;
}


int do_user_logout(int fd) {
	struct klist_node 	*n;
	struct user 		*usr;
	static const char 	*leave_msg = "%s left, now there are %d users here. \n";
	char 			*t;

	if (unlikely(!(n = find_user(fd)))) 
		return 1;

	usr = n->data;

	if (unlikely(!(t = amalloc(128)))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	sync_user(usr->username, usr);

	if (!strncmp(usr->username, "system", USRNAME_SIZE)) 
		system_fd = 0;

	vcc_log("Bye, %s. \n", usr->username);

	quit_all_session(fd);

	nusers--;

	sprintf(t, leave_msg, usr->username, nusers);
	vcc_message_broadcast(t, "system", fd, 0);

	klist_del(&logged_users, n);

	afree(usr, sizeof(struct user));
	afree(n, sizeof(struct klist_node));

	return 0;
}


int incr_score(int fd, struct vcc_request *req) {
	struct vcc_request 	*out;
	struct user 		u;

	if (unlikely(!(out = amalloc(sizeof(struct vcc_request ))))) {
		vcc_log("*** malloc() failed\n");

		return 1;
	}

	out->magic = htonl(VCC_MAGIC);
	out->reqtype = htonl(REQ_SYS_SCRINC);

	if (fd != system_fd) {
		out->uid = htonl(-1);
		vcc_send_req(find_connection(fd), out);

		vcc_log("%d:%s tried to modify the score. \n", fd, req->usrname);
		vcc_info("WSCRMOD %s %d\n", req->usrname, fd);

		return 1;
	}

	if (unlikely(get_user(req->usrname, &u) == -1)) {
		out->uid = htonl(-1);
		vcc_send_req(find_connection(fd), out);

		return 1;
	}

	u.score += ntohl(req->session);

	if (u.score > 65535) {
		u.level -= u.score >> 16;
		u.score %= 65535;
	}

	sync_user(req->usrname, &u);

	out->uid = 0;
	vcc_send_req(find_connection(fd), out);

	vcc_log("%s is %d:%d scores now. \n", req->usrname, u.level, u.score);

	return 0;
}


